package com.will.txj.aj.security.config;

import com.will.txj.aj.security.config.auth.AJUserDetailsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.session.InvalidSessionStrategy;
import org.springframework.security.web.session.SessionInformationExpiredStrategy;

import javax.annotation.Resource;
import javax.sql.DataSource;

/**
 * @author: wen-yi;
 * @date: 2021/11/29 16:00;
 * @Description:
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 获取数据库真实数据
     */
    @Resource
    private AJUserDetailsService ajUserDetailsService;
    /**
     * 加密校验
     */
    @Resource
    private PasswordEncoder passwordEncoder;
    /**
     * 记住我配置数据源
     */
    @Autowired
    private DataSource dataSource;
    /**
     * 记住我配置数据存储
     */
    @Autowired
    private PersistentTokenRepository tokenRepository;
    /**
     * 登录成功的处理方案
     */
    @Autowired
    private AuthenticationSuccessHandler myAuthenticationSuccessHandler;
    /**
     * 失败的处理方案
     */
    @Autowired
    private AuthenticationFailureHandler myAuthenticationFailureHandler;
    /**
     * 注销操作处理器
     */
    @Autowired
    private LogoutHandler myLogoutHandler;
    /**
     * 注销成功处理器
     */
    @Autowired
    private LogoutSuccessHandler myLogoutSuccessHandler;
    /**
     * Session超时处理
     */
    @Autowired
    private InvalidSessionStrategy myInvalidSessionStrategy;
    /**
     * 被挤下线时的操作
     */
    @Autowired
    private SessionInformationExpiredStrategy myExpiredSessionStrategy;
    /**
     * 用户密码校验过滤器
     */
//    @Autowired
//    private AbstractAuthenticationProcessingFilter adminAuthenticationProcessingFilter;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 配置记住我 数据存储的位置
     * @return
     */
    @Bean
    public PersistentTokenRepository tokenRepository(){
        //一般选择 存储jdbc 而不是内存
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        //设置数据源
        tokenRepository.setDataSource(dataSource);
        //启动时是否创建表，第一次要,之后注释掉
        //tokenRepository.setCreateTableOnStartup(true);
        return tokenRepository;
    }

    /**
     * 账户进行授权
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 内存中进行新增用户 authorities:增加权限  动态配置用户权限 并设置密码加密方式
        auth.userDetailsService(ajUserDetailsService).passwordEncoder(passwordEncoder);
    }

    /**
     * 配置拦截规则
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置认证方式 token 形式：不需要弹出页面 form表单：比较传统的项目 动态配置地址权限
        // csrf: 防止跨域伪造
        http.csrf().disable()
            // 自定义过滤器认证用户名密码
            //.addFilterAt(adminAuthenticationProcessingFilter, UsernamePasswordAuthenticationFilter.class)
            // 重定向登录地址
            //.exceptionHandling().authenticationEntryPoint(new AjaxAuthenticationEntryPoint("/pages/login")).and()
            .authorizeRequests()
            .antMatchers("/pages/**","/resource/**","/auth/**","/captcha/**").permitAll()
            .antMatchers("/**").fullyAuthenticated()
            // 表单登录配置
            .and().formLogin().loginPage("/pages/login").loginProcessingUrl("/auth/login").failureHandler(myAuthenticationFailureHandler).successHandler(myAuthenticationSuccessHandler)
            // 记住我配置
            .and().rememberMe().userDetailsService(ajUserDetailsService).tokenRepository(tokenRepository).tokenValiditySeconds(3600*24*7)
            // 退出配置
            .and().logout().logoutUrl("/auth/logout").addLogoutHandler(myLogoutHandler).logoutSuccessHandler(myLogoutSuccessHandler).deleteCookies("OAUTH2-CLIENT-SESSIONID-12000")
            // 会话配置 ALWAYS|IF_REQUIRED(默认)|NEVER|STATELESS(前后端分离无状态应用|节省资源)
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                //登录超时
                .invalidSessionStrategy(myInvalidSessionStrategy)
                //最大登录数为1
                .maximumSessions(1)
                //false:允许别人登录  true:不允许别人登录
                .maxSessionsPreventsLogin(false)
                //被挤下线处理
                .expiredSessionStrategy(myExpiredSessionStrategy);
    }


    /**
     * 用户密码模式 必须配置 授权管理器
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
        //return new CusAuthenticationManager(new AdminAuthenticationProvider());
    }
}
